﻿// XmlVisualizer
// Copyright (C) 2009  Roman Betík - rbetik@gmail.com

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

using System;
using System.Configuration;
using System.Diagnostics;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Windows.Threading;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Linq;
using System.Linq;
using System.IO;
using System.Threading;
using System.Xml.XPath;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;

using XmlVisualizer.Models;
using XmlVisualizer.Controllers;
using XmlVisualizer.Controls;
using XmlVisualizer.DiagramDesigner;
using XmlVisualizer.DiagramDesigner.Controls;

namespace XmlVisualizer {
	public partial class Document : Window {
		#region XML PART
		/// <summary>
		/// Calls methods that create visual elements for current loaded xml document
		/// and connect them.
		/// </summary>
		public void DrawXml(XNodeCollection nodes, bool clearCanvas, bool connectWithAlreadyPlaced) {
			if (clearCanvas) {
				this.Dispatcher.Invoke((Action)(() => {
					ClearCanvas();
				}));
			}

			this.Dispatcher.Invoke((Action)(() => {
				CreateDesignerItems(nodes);
			}));

			this.Dispatcher.BeginInvoke((Action)(() => {
				StatusProgress(0);
				PlaceUIElementOnCanvas(0, nodes, connectWithAlreadyPlaced);
			}));
		}

		/// <summary>
		/// Creates DesignerItems for all elements in current document.
		/// </summary>
		private void CreateDesignerItems(XNodeCollection nodes) {
			foreach (XNodeInfo info in nodes.Values) {
				DesignerItem item = CreateUIElement(info);
				if (item != null)
					info.UI = item;
			}
		}

		/// <summary>
		/// Creates ucElement instance for XNodeInfo.
		/// </summary>
		/// <param name="info">XNodeInfo about XNode</param>
		/// <returns>Reference to created DesignerItem if everything went ok or null.</returns>
		/// <exception cref="System.ArgumentNullException">Thrown when parameter info is null.</exception>
		private DesignerItem CreateUIElement(XNodeInfo info) {
			if (info == null)
				throw new ArgumentNullException("info");

			if (info.Node != null) {
				if (String.IsNullOrEmpty(info.AppearanceTemplate))
					info.AppearanceTemplate = Properties.Settings.Default.defaultAppearance; //DesignerItem.AppearanceDefaultTemplateName;

				ControlTemplate appearance = FindResource(info.AppearanceTemplate) as ControlTemplate;
				DesignerItem newItem = new DesignerItem(appearance, info);
				newItem.Padding = new Thickness(10); // to do: don't leave this constant here
				newItem.Width = Properties.Settings.Default.defaultSize.X;//150; // to do: --||--

				if (info.Node is XElement) {
					XElement element = info.Node as XElement;
					newItem.Element = element;
					newItem.Node = element;

					ucElement content = new ucElement(element);
					newItem.Content = content;

					return newItem;
				} else if (info.Node is XText) {
					XText node = info.Node as XText;
					newItem.Element = null;
					newItem.Node = node;

					ucTextNode content = new ucTextNode(node);
					newItem.Content = content;

					return newItem;
				} else {
					return null;
				}
			} else {
				return null;
			}
		}

		private delegate void PlaceElementDelegate(params object[] args);

		private void StatusProgress(double progress) {
			statusProgressBar.Visibility = Visibility.Visible;
			statusTextPercentage.Visibility = Visibility.Visible;
			statusTextPercentageCaption.Visibility = Visibility.Visible;

			statusProgressBar.Value = progress;
			statusTextPercentage.Text = String.Format("{0}%", Math.Round(statusProgressBar.Value, 0));
		}

		private void HideProgress() {
			statusProgressBar.Visibility = Visibility.Collapsed;
			statusTextPercentage.Visibility = Visibility.Collapsed;
			statusTextPercentageCaption.Visibility = Visibility.Collapsed;
		}

		private void PlaceUIElementOnCanvas(int i, XNodeCollection nodes, bool connectWithAlreadyPlaced) {
			XNodeInfo info = nodes.Values.ElementAtOrDefault(i);
			if (info != null) {
				if (info.UI is DesignerItem) {
					AddDisplayedNode(info);
					PlaceUIElementOnCanvas(info.UI as DesignerItem, info.Position);
					StatusProgress(((double)i / (double)nodes.Values.Count) * 100);
				}

				object[] arr = new object[] { i, nodes };

				DispatcherOperation op = designerCanvas.Dispatcher.BeginInvoke(DispatcherPriority.Background,
					(Action)(() => {
						this.PlaceUIElementOnCanvas(i + 1, nodes, connectWithAlreadyPlaced);
					}));
			} else { // call MakeConnections
				HideProgress();

				this.Dispatcher.Invoke((Action)(() => {
					MakeConnectionsUpwards(nodes, connectWithAlreadyPlaced);
				}));
			}
		}

		/// <summary>
		/// Places all DesignerItems in nodes collection onto Canvas.
		/// </summary>
		/// <param name="nodes">nodes to place onto Canvas.</param>
		private void PlaceUIElementsOnCanvas(XNodeCollection nodes) {
			XNodeCollection elements = nodes;

			foreach (XNodeInfo info in elements.Values) {
				if (info.UI is DesignerItem)
					PlaceUIElementOnCanvas(info.UI as DesignerItem, info.Position);
			}
		}

		/// <summary>
		/// Places one DesignerItem onto Canvas.
		/// </summary>
		/// <param name="item">item to place</param>
		/// <param name="position">position where to put it</param>
		private void PlaceUIElementOnCanvas(DesignerItem item, Point position) {
			DesignerCanvas.SetLeft(item, position.X);
			DesignerCanvas.SetTop(item, position.Y);

			Canvas.SetZIndex(item, designerCanvas.Children.Count);
			designerCanvas.Children.Add(item);
			designerCanvas.SetConnectorDecoratorTemplate(item);
		}
		
		/// <summary>
		/// Connects nodes in nodes collection upwards. It means starting from the nodes that are deepest in the hierarchy
		/// and going upwards.
		/// </summary>
		/// <param name="nodes">collection of nodes to connect.</param>
		/// <param name="connectWithAlreadyPlaced">if true, it tries to connect with parent nodes that are not in the nodes collection but might already be on the canvas.</param>
		private void MakeConnectionsUpwards(XNodeCollection nodes, bool connectWithAlreadyPlaced) {
			int maxDepth = (from node in nodes.Values where node.Depth == nodes.Values.Max(c => c.Depth) select node.Depth).FirstOrDefault();
			int depth = maxDepth;

			IEnumerable<XNodeInfo> currentRow = from node in nodes.Values where node.Depth == depth select node;
			while (currentRow.Count() > 0) {
				foreach (XNodeInfo nodeInfo in currentRow) {
					XNode node = nodeInfo.Node;
					XNode parentNode = node.Parent;

					if (parentNode != null) {
						if (nodes.ContainsKey(parentNode)) {
							XNodeInfo parentNodeInfo = nodes[parentNode];
							ConnectNodes(parentNodeInfo, nodeInfo);
						}
						if (connectWithAlreadyPlaced) {
							if (this.displayedNodes.ContainsKey(parentNode)) {
								XNodeInfo parentNodeInfo = displayedNodes[parentNode];
								ConnectNodes(parentNodeInfo, nodeInfo);
							}
						}
					}
				}
				depth--;
				currentRow = from node in nodes.Values where node.Depth == depth select node;
			}
		}

		/// <summary>
		/// Connects two DesignerItems on canvas with Connection.
		/// </summary>
		/// <param name="parentNode">XNodeInfo associated with parent node.</param>
		/// <param name="childNode">XNodeInfo associated with child node.</param>
		private void ConnectNodes(XNodeInfo parentNode, XNodeInfo childNode) {
			if (parentNode.UI is DesignerItem && childNode.UI is DesignerItem) {
				DesignerItem parent = parentNode.UI as DesignerItem;
				DesignerItem child = childNode.UI as DesignerItem;

				Connector bottom = parent.GetConnector(Connectors.Bottom);
				Connector top = child.GetConnector(Connectors.Top);

				// connection connecting two connectors
				Connection conn = new Connection(bottom, top);
				Canvas.SetZIndex(conn, designerCanvas.Children.Count);
				designerCanvas.Children.Add(conn);
			}
		}

		/// <summary>
		/// Connects nodes according to their parent - child relationship.
		/// </summary>
		private void MakeConnections(XNodeCollection nodes) {
			try {
				int d = 0;
				XNodeCollection elements = nodes;
				IEnumerable<XNodeInfo> selectedNodes = from node in elements.Values where node.Depth == d select node; // select nodes from the right depth
				while (selectedNodes.Count() > 0) { // loop until there are any nodes to connect
					foreach (XNodeInfo parentInfo in selectedNodes) {
						XNode parentNode = parentInfo.Node as XNode;

						if (parentNode is XElement) { // only elements can have child elements
							XElement parent = parentNode as XElement;

							if (parent.Nodes().Count() > 0) {
								XNode first = parent.Nodes().FirstOrDefault();
								XNodeInfo firstInfo = elements[first];

								if (firstInfo != null) {
									foreach (XNode child in parent.Nodes()) {
										XNodeInfo childInfo = elements[child];

										if (childInfo != null) {
											ConnectNodes(parentInfo, childInfo);
										}
									}
								}
							}
						}
					}
					d++;
					selectedNodes = from node in elements.Values where node.Depth == d select node;
				}
			} catch (Exception ex) {
				Exceptions.ExceptionHandling.HandleException(ex);
			}
		}
		#endregion
	}
}